package com.tool.hooker_inject;

import android.annotation.TargetApi;
import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.hardware.display.DisplayManager;
import android.os.Build;
import android.util.DisplayMetrics;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;


public class DisplayHooker {
    private static int _width = -1;
    private static int _height = -1;
    private static float _density = -1;
    private static boolean _flag = false;
    private static boolean _pkgHook = false;

    private static Object _proxy;
    private static Object displayManager;
    private static Field mDisplayListeners;

    public static class DisplayException extends Exception {
        public DisplayException(String message) {
            super(message);
        }

        public DisplayException(String message, Throwable cause) {
            super(message, cause);
        }
    }

    private static Object getDisplayManager() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        if (displayManager == null) {
            final Method getInstance = Class.forName("android.hardware.display.DisplayManagerGlobal").getDeclaredMethod("getInstance");
            getInstance.setAccessible(true);
            displayManager = getInstance.invoke(null);
        }

        return displayManager;
    }

    private static void hook(Context context) throws DisplayException {
        try {
            final Object displayManagerGlobal = getDisplayManager();

            final Object mDm = Reflect.get(displayManagerGlobal, "mDm");

            _proxy = Proxy.newProxyInstance(DisplayHooker.class.getClassLoader(), mDm.getClass().getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    if ("getDisplayInfo".equals(method.getName()) || "getDisplayInfoEx".equals(method.getName())
                            || "getDisplayInfoUsingAppToken".equals(method.getName())) {
                        Object info = method.invoke(mDm, args);

                        boolean f = false;
                        final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
                        for (StackTraceElement stackTraceElement : stackTrace) {
                            String mName = stackTraceElement.getMethodName();
                            String clName = stackTraceElement.getClassName();
                            if ((clName.contains("WebView") && !"onPageFinished".equals(mName)) || mName.contains("onDisplayChanged")) {
                                f = true;
                                break;
                            }
                        }

                        if (!f)
                            return info;

                        try {
                            if (_width != -1) {
                                Reflect.set(info, "appWidth", _width);
                                Reflect.set(info, "logicalWidth", _width);
                            }

                            if (_height != -1) {
                                Reflect.set(info, "appHeight", _height);
                                Reflect.set(info, "logicalHeight", _height);
                            }

                            if (_density != -1)
                                Reflect.set(info, "logicalDensityDpi", (int) (_density * 160));

                            Reflect.set(info, "rotation", _flag ? 1 : 0);
                        } catch (Exception e) {

                        }

                        return info;
                    }

                    return method.invoke(mDm, args);
                }
            });

            Reflect.set(displayManagerGlobal, "mDm", _proxy);

            if (!_pkgHook && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
                hookBase(context);
        } catch (Throwable e) {
            throw new DisplayException("hook failed", e);
        }
    }

    private static void hookBase(Context context) {
        Object base =  Reflect.get(context, "mBase");
        if(base == null)
            return;

        if(base instanceof  XYWrapper)
        {
            return;
        }
        else{
            XYWrapper xyWrapper = new XYWrapper((Context) base);

            Reflect.setAll(context.getApplicationContext(), "mBase", xyWrapper);
        }
    }

    public static Resources proxyRes(Resources res) {
        return new Resources(res.getAssets(), res.getDisplayMetrics(), res.getConfiguration()) {
            @Override
            public DisplayMetrics getDisplayMetrics() {

                DisplayMetrics displayMetrics = super.getDisplayMetrics();

                boolean f = false;
                final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
                for (StackTraceElement stackTraceElement : stackTrace) {
                    if (stackTraceElement.getClassName().contains("chromium")) {
                        f = true;
                        break;
                    }
                }

                if (!f)
                    return displayMetrics;

                if (_width != -1)
                    displayMetrics.widthPixels = _width;
                if (_height != -1)
                    displayMetrics.heightPixels = _height;
                if (_density != -1)
                    displayMetrics.density = _density;

                return displayMetrics;
            }
        };
    }

    public static void refresh(Context context, int width, int height, float density) throws Exception {
        _width = width;
        _height = height;
        _density = density;
        update(context);
        update(context);
    }

    private static void update(Context context) throws DisplayException {
        _flag = !_flag;

        if (_proxy == null)
            hook(context);

        if (Build.VERSION.SDK_INT <= 19) {
            try {
                updateCallBack(context);
            } catch (DisplayException e) {
                throw new DisplayException("update fialed", e);
            }
        } else {
            try {
                update();
            } catch (Exception e) {
                throw new DisplayException("update fialed", e);
            }
        }
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    private static void update() throws DisplayException {
        try {
            if (mDisplayListeners == null) {
                mDisplayListeners = getDisplayManager().getClass().getDeclaredField("mDisplayListeners");
                mDisplayListeners.setAccessible(true);
            }

            final List list = (List) mDisplayListeners.get(getDisplayManager());
            for (Object lis : list) {
                final Field mListener = lis.getClass().getDeclaredField("mListener");
                mListener.setAccessible(true);

                final DisplayManager.DisplayListener listener = (DisplayManager.DisplayListener) mListener.get(lis);

                final String name = listener.getClass().getClassLoader().toString().toLowerCase();
                if (name.contains("webview") || name.contains("chrome") || name.contains("android"))
                    listener.onDisplayChanged(0);
            }
        } catch (Throwable e) {
            throw new DisplayException("notify display changed failed", e);
        }
    }

    private static void updateCallBack(Context context) throws DisplayException {

        try {
            Configuration conf = context.getResources().getConfiguration();
            Object orientation = conf.orientation;

            conf = new Configuration(conf);

            if (orientation.equals(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE))
                conf.orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
            else {
                conf.orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
            }

            final List<ComponentCallbacks> mComponentCallbacks = Reflect.get(context.getApplicationContext(), "mComponentCallbacks");

            for (ComponentCallbacks mComponentCallback : mComponentCallbacks) {
                if (mComponentCallback.getClass().getName().contains("DeviceDisplayInfo"))
                    mComponentCallback.onConfigurationChanged(conf);
            }

        } catch (Throwable e) {
            throw new DisplayException("notifiy configuration changed failed", e);
        }
    }

    public static void setPkgHook(boolean _pkgHook) {
        DisplayHooker._pkgHook = _pkgHook;
    }

    public static class XYWrapper extends ContextWrapper
    {

        public XYWrapper(Context base) {
            super(base);
        }

        @Override
        public Resources getResources() {
            Resources res = super.getResources();
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
                return DisplayHooker.proxyRes(res);
            else
                return res;
        }
    }

}
